package club.xiaojiawei.dp;

import java.util.Arrays;

/**
 * @author 肖嘉威
 * @version 1.0
 * @date 6/4/22 1:36 AM
 * @question 322. 零钱兑换
 * @description 给你一个整数数组 coins ，表示不同面额的硬币；以及一个整数 amount ，表示总金额。
 * 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额，返回 -1 。
 * 你可以认为每种硬币的数量是无限的。
 */
public class CoinChange322 {

    public static void main(String[] args) {
        CoinChange322 test = new CoinChange322();
        int result = test.coinChange2(new int[]{2, 5, 10, 1}, 27);
        System.out.printf("最少需要%s个硬币\n", result);
    }

    /**
     * dp
     * 时间复杂度O(Sn)
     * 空间复杂度O(S)
     * @param coins
     * @param amount
     * @return
     */
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
//        因为根据题意amount最大为10000（即最多需要10000个1元硬币），所以这里取10001表示无法凑成（可以再优化成 amount+1 表示无法凑成）
//        Arrays.fill(dp, 10001);
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        for (int coin : coins) {
            for (int i = coin; i <= amount; i++){
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
//        return dp[amount] >= 10001? -1 : dp[amount];
        return dp[amount] > amount? -1 : dp[amount];
    }

    /**
     * 官方-记忆化搜索（递归回溯）
     * 时间复杂度O(Sn)
     * 空间复杂度O(S)
     * @param coins
     * @param amount
     * @return
     */
    public int coinChange2(int[] coins, int amount) {
        if (amount < 1) {
            return 0;
        }
        return coinChange(coins, amount, new int[amount]);
    }

    private int coinChange(int[] coins, int rem, int[] count) {
        if (rem < 0) {
            return -1;
        }
        if (rem == 0) {
            return 0;
        }
        if (count[rem - 1] != 0) {
            return count[rem - 1];
        }
        int min = Integer.MAX_VALUE;
        for (int coin : coins) {
            int res = coinChange(coins, rem - coin, count);
            if (res >= 0 && res < min) {
                min = 1 + res;
            }
        }
        count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
        return count[rem - 1];
    }
}
